package com.googlecode.tidej.shared.jsdoc;

import com.googlecode.tidej.shared.model.Library;
import com.googlecode.tidej.shared.model.Operation;
import com.googlecode.tidej.shared.model.Parameter;
import com.googlecode.tidej.shared.model.Property;
import com.googlecode.tidej.shared.model.Type;

public class JsDocSerializer {
 
  public static String serializeProject(Library project) {
    StringBuilder sb = new StringBuilder();
    sb.append(serializeType(project.getGlobals()));
    for (Type t: project.getTypes()) {
      sb.append(serializeType(t));
    }
    
    sb.append(project.getGlobals().getConstructor().getBody());
    sb.append("\n");
    return sb.toString();
  }
  
  public static String serializeType(Type type) {
    StringBuilder sb = new StringBuilder();
    boolean global = type.getLibrary().getGlobals() == type;
    
    if (global) {
      for(Property property: type.getOwnProperties()) {
        sb.append(serializeProperty(property, true));
      }
    } else {
      sb.append(serializeOperationHead(type.getConstructor(), true, false));
      for(Property property: type.getOwnProperties()) {
        sb.append(serializeProperty(property, false));
      }
      sb.append(serializeOperationBody(type.getConstructor()));
    }
    
    for(Operation operation: type.getOwnOperations()) {
      sb.append(serializeOperation(operation, global));
    }
    return sb.toString();
  }
  
  public static String serializeOperation(Operation operation, boolean global) {
    StringBuilder sb = new StringBuilder();
    sb.append(serializeOperationHead(operation, false, global));
    sb.append(serializeOperationBody(operation));
    return sb.toString();
  }

  private static String serializeProperty(Property property, boolean global) {
    String indent = global ? "\n" : "\n  ";
    StringBuilder sb = new StringBuilder(indent);
    sb.append("/**");
    String doc = property.getDocumentation();
    if (doc != null && doc.trim().length() > 0) {
      for (String part: doc.split("\n")) {
        sb.append(indent);
        sb.append(" * ");
        sb.append(part);
      }
      sb.append(indent);
      sb.append(" * ");
    }
    if (property.getType() != null) {
      sb.append(indent);
      sb.append(" * @type {" + property.getType().getName() + "}");
    }
    sb.append(indent);
    sb.append(" */");
    String initialValue = property.getInitialValue();
    if (initialValue == null || "".equals(initialValue)) {
      initialValue = "undefined";
    }
    
    sb.append(indent);
    if (!global) {
      sb.append("this.");
    }
    sb.append(property.getName() + " = " + initialValue + ";\n");
    return sb.toString();
  }
  
  private static String serializeOperationHead(Operation operation, 
      boolean constructor, boolean global) {
    String indent = "\n";
    StringBuilder sb = new StringBuilder(indent);
    sb.append("/**");
    String doc = operation.getDocumentation();
    if (doc != null && doc.trim().length() > 0) {
      for (String part: operation.getDocumentation().split("\n")) {
        sb.append(indent);
        sb.append(" * ");
        sb.append(part);
      }
      sb.append(indent);
      sb.append(" * ");
    }
    
    if (constructor) {
      sb.append(indent);
      sb.append(" * @constructor");
    }
    for (Parameter p: operation.getParameters()) {
      sb.append(indent);
      sb.append(" * @param {");
      sb.append(p.getType() == null ? "*" : p.getType().getName());
      sb.append("} "); 
      sb.append(p.getName());
      if (p.getDocumentation() != null) {
        sb.append(' ');
        sb.append(p.getDocumentation());
      }
    }
    
    if (!constructor && operation.getType() != null) {
      sb.append(indent);
      sb.append(" * @return {");
      sb.append(operation.getType().getName());
      sb.append("}");
    }
    sb.append(indent);
    sb.append(" */");
    if (global) {
      sb.append(indent);
      sb.append("function " + operation.getName() + "(");
    } else {
      sb.append(indent);
      sb.append(operation.getOwner().getName());
      if (!constructor) {
        sb.append(".prototype.");
        sb.append(operation.getName());
      }
      sb.append(" = function(");
    }
    boolean first = true;
    for (Parameter p: operation.getParameters()) {
      if (first) {
        first = false;
      } else {
        sb.append(", ");
      }
      sb.append(p.getName());
    }
    sb.append(") {");
    return sb.toString();
  }
  
  private static String serializeOperationBody(Operation operation) {
    String body = operation.getBody();
    String indent = "\n  ";
    String[] lines = body == null ? new String[0] : body.split("\n");
    int start = 0;
    while (start < lines.length && lines[start].trim().length() == 0) {
      start++;
    }
    int end = lines.length - 1;
    while (end >= 0 && lines[end].trim().length() == 0) {
      end--;
    }
    StringBuilder sb = new StringBuilder();
    for (int i = start; i <= end; i++) {
      sb.append(indent);
      sb.append(lines[i]);
    }
    sb.append(indent.substring(0, indent.length() - 2));
    sb.append("}\n");
    return sb.toString();
  }
}
